/*
   Copyright 2010 jcgarciam [at] gmail [dot] com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.OleDb;
using System.Data.Sql;
using System.Data;
using System.Data.SqlClient;

namespace Cruddy.Persistence
{
    class TransactionManager : IDisposable
    {
        private Stack<SqlTransaction> transactions;
        private WeakReference connRef;
        private IsolationLevel isolationLevel;

        private const int MAX_NESTED_TRANSACTIONS = 5;
        public  const string PENDING_TRANSACTIONS_MSG = "There are pending transactions and a rollback was issue on them before closing the connection. This is likely a bug in your code.";
        public const string CONNECTION_NOT_LONGER_ALIVE = "Connection is not longer alive, therefore this instance is not valid anymore.";
        public const string MAX_NESTED_TRANSACTIONS_REACHED = "The maximum allow of nested transactions were reached";

        public TransactionManager(SqlConnection connection, IsolationLevel level)
        {
            transactions = new Stack<SqlTransaction>(MAX_NESTED_TRANSACTIONS);
            connRef = new WeakReference(connection);
            isolationLevel = level;
        }

        /// <summary>
        ///  Inform if there are pending transactions
        /// </summary>
        /// <returns>bool</returns>
        public bool HasPendingTransaction()
        {
            return transactions.Count > 0;
        }
        /// <summary>
        ///  Start a new transaction using the define Isoloation Level and put it the transaction Queue
        /// </summary>
        public void BeginTransaction()
        {
            if (transactions.Count >= MAX_NESTED_TRANSACTIONS)
            {
                throw new Exceptions.PersistanceManagerException(MAX_NESTED_TRANSACTIONS_REACHED);
            }
            SqlConnection xcon = GetConnection();
            if (xcon.State != ConnectionState.Open) { xcon.Open(); }
            SqlTransaction trx = xcon.BeginTransaction(isolationLevel);
            transactions.Push(trx);
        }
        /// <summary>
        ///  Commit current Transaction
        /// </summary>
        public void CommitTransaction()
        {
            if (HasPendingTransaction())
            {
                SqlTransaction trans = transactions.Pop();
                trans.Commit();
                trans.Dispose();
            }
        }
        /// <summary>
        ///  Rollback current Transaction
        /// </summary>
        public void RollbackTransaction()
        {
            if (HasPendingTransaction())
            {
                SqlTransaction trans = transactions.Pop();
                trans.Rollback();
                trans.Dispose();
            }
        }
        /// <summary>
        ///   Retrieve the current transaction
        /// </summary>
        /// <returns>active SqlTransaction</returns>
        public SqlTransaction GetCurrentTransaction()
        {
            if (HasPendingTransaction())
            {
                return transactions.Peek();
            }
            return null;
        }

        /// <summary>
        ///  Get the underlying connection from the internal Storage
        /// </summary>
        /// <returns></returns>
        private SqlConnection GetConnection()
        {
            SqlConnection xcon = (SqlConnection)connRef.Target;
            if (xcon != null)
            {
                return xcon;
            }
            else
            {
                throw new Exceptions.PersistanceManagerException(CONNECTION_NOT_LONGER_ALIVE);
            }
        }

        /// <summary>
        ///  Rollback all pending transactions
        /// </summary>
        public void RollbackPendingTransactions()
        {
            bool found = false;
            while (HasPendingTransaction())
            {
                found = true;
                RollbackTransaction();
            }
            if (found)
            {
                throw new Exceptions.PersistanceManagerException(PENDING_TRANSACTIONS_MSG);
            }
        }
        #region IDisposable Members

        public void Dispose()
        {
            connRef.Target = null;
            connRef = null;
            RollbackPendingTransactions();
            transactions = null;
        }

        #endregion
    }
}
